/*
 * Copyright oVirt Authors
 * SPDX-License-Identifier: Apache-2.0
*/

package org.ovirt.engine.api.v3.helpers;

import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toSet;

import java.util.Objects;
import java.util.Set;

import org.ovirt.engine.api.model.Network;
import org.ovirt.engine.api.model.Nic;
import org.ovirt.engine.api.model.Vm;
import org.ovirt.engine.api.model.VnicProfile;
import org.ovirt.engine.api.resource.ClusterNetworksResource;
import org.ovirt.engine.api.resource.ClusterResource;
import org.ovirt.engine.api.resource.ClustersResource;
import org.ovirt.engine.api.resource.NetworkResource;
import org.ovirt.engine.api.resource.NetworksResource;
import org.ovirt.engine.api.resource.SystemResource;
import org.ovirt.engine.api.resource.VmResource;
import org.ovirt.engine.api.resource.VmsResource;
import org.ovirt.engine.api.resource.VnicProfileResource;
import org.ovirt.engine.api.resource.VnicProfilesResource;
import org.ovirt.engine.api.restapi.resource.BackendApiResource;
import org.ovirt.engine.api.v3.types.V3NIC;
import org.ovirt.engine.api.v3.types.V3Network;
import org.ovirt.engine.api.v3.types.V3Networks;
import org.ovirt.engine.api.v3.types.V3PortMirroring;

/**
 * This class contains a set of methods useful to handle backwards compatibility issues related to NICs.
 */
public class V3NICHelper {
    /**
     * Finds the VNIC profile that correspond to the given V3 NIC and assigns it to the given V4 NIC.
     *
     * @param vmId the identifier of the virtual machine that the NIC is going to be added to
     * @param v3Nic the V3 NIC where the information will be extracted from
     * @param v4Nic the V4 NIC that will be populated
     */
    public static void setVnicProfile(String vmId, V3NIC v3Nic, Nic v4Nic) {
        // Do nothing if the profile is already set:
        if (v4Nic.isSetVnicProfile()) {
            return;
        }

        // In version 4 of the API the "network" and "port_mirroring" properties of the NIC have been completely
        // removed, and instead of using them it is required to specify a VNIC profile. This means that if the user
        // isn't explicitly indicating the VNIC profile we need to find one that is compatible with the given network
        // and port mirroring configuration. The only VNIC profiles to consider are the ones corresponding to the
        // networks of the cluster where the VM resides, so we need to retrieve the VM, then the cluster, and then the
        // identifiers of the networks:
        SystemResource systemService = BackendApiResource.getInstance();
        VmsResource vmsService = systemService.getVmsResource();
        VmResource vmService = vmsService.getVmResource(vmId);
        Vm vm = vmService.get();
        ClustersResource clustersService = systemService.getClustersResource();
        ClusterResource clusterService = clustersService.getClusterResource(vm.getCluster().getId());
        ClusterNetworksResource networksService = clusterService.getNetworksResource();
        Set<String> validNetworkIds = networksService.list().getNetworks().stream()
            .map(Network::getId)
            .collect(toSet());

        // Find a VNIC profile that is in the set of valid networks and that is compatible with the NIC:
        VnicProfilesResource profilesService = systemService.getVnicProfilesResource();
        profilesService.list().getVnicProfiles().stream()
            .filter(profile -> validNetworkIds.contains(profile.getNetwork().getId()))
            .filter(profile -> isProfileCompatible(profile, v3Nic))
            .sorted(comparing(VnicProfile::getName))
            .map(VnicProfile::getId)
            .findFirst()
            .ifPresent(id -> {
                VnicProfile v4Profile = new VnicProfile();
                v4Profile.setId(id);
                v4Nic.setVnicProfile(v4Profile);
            });
    }

    /**
     * Populates the {@code network} and {@code port_mirroring} attributes used in V3.
     *
     * @param v4Nic the V4 NIC where the details of the NIC profile will be extracted from
     * @param v3Nic the V3 NIC object that will be populated
     */
    public static void setNetworkAndPortMirroring(Nic v4Nic, V3NIC v3Nic) {
        // Do nothing if the V4 NIC doesn't specify a profile:
        VnicProfile v4Profile = v4Nic.getVnicProfile();
        if (v4Profile == null) {
            return;
        }

        // Retrieve the complete representation of the profile, as we need it to compute the "network" and
        // "port_mirroring" attributes used in V3:
        BackendApiResource systemService = BackendApiResource.getInstance();
        VnicProfilesResource profilesService = systemService.getVnicProfilesResource();
        VnicProfileResource profileService = profilesService.getProfileResource(v4Profile.getId());
        v4Profile = profileService.get();

        // Populate the "network" and "port_mirroring" attributes of the V3 object:
        Network v4Network = v4Profile.getNetwork();
        V3Network v3Network = new V3Network();
        v3Network.setId(v4Network.getId());
        v4Network.setHref(v4Network.getHref());
        v3Nic.setNetwork(v3Network);
        if (v4Profile.isSetPortMirroring() && v4Profile.isPortMirroring()) {
            V3PortMirroring v3PortMirroring = new V3PortMirroring();
            V3Networks v3PortMirroringNetworks = new V3Networks();
            V3Network v3PortMirroringNetwork = new V3Network();
            v3PortMirroringNetwork.setId(v4Network.getId());
            v3PortMirroringNetwork.setHref(v4Network.getHref());
            v3PortMirroringNetworks.getNetworks().add(v3PortMirroringNetwork);
            v3PortMirroring.setNetworks(v3PortMirroringNetworks);
            v3Nic.setPortMirroring(v3PortMirroring);
        }
    }

    /**
     * Checks if the given VNIC profile is compatible with the given NIC.
     *
     * @param profile the VNIC profile to check
     * @param nic the NIC to check
     * @return {@code true} iff the profile is compatible with the network and port mirroring configuration of the NIC
     */
    private static boolean isProfileCompatible(VnicProfile profile, V3NIC nic) {
        // Retrieve the representation of the network corresponding to the profile, as we are going to need it in
        // order to check the name:
        SystemResource systemService = BackendApiResource.getInstance();
        NetworksResource networksService = systemService.getNetworksResource();
        NetworkResource networkService = networksService.getNetworkResource(profile.getNetwork().getId());
        Network profileNetwork = networkService.get();

        // If the NIC configuration explicitly specifies a network then the profile has to correspond to that same
        // network:
        V3Network nicNetwork = nic.getNetwork();
        if (nicNetwork != null) {
            if (nicNetwork.isSetId()) {
                if (!Objects.equals(profileNetwork.getId(), nicNetwork.getId())) {
                    return false;
                }
            }
            if (nicNetwork.isSetName()) {
                if (!Objects.equals(profileNetwork.getName(), nicNetwork.getName())) {
                    return false;
                }
            }
        }

        // If the NIC configuration explicitly specifies a port mirroring configuration then the profile must have
        // port mirroring enabled, and all the networks included in the port mirroring configuration must be the same
        // network used by the profile:
        V3PortMirroring nicPortMirroring = nic.getPortMirroring();
        if (nicPortMirroring != null) {
            if (!profile.isSetPortMirroring() || !profile.isPortMirroring()) {
                return false;
            }
            V3Networks nicPortMirroringNetworks = nicPortMirroring.getNetworks();
            if (nicPortMirroringNetworks != null) {
                for (V3Network nicPortMirroringNetwork : nicPortMirroringNetworks.getNetworks()) {
                    if (nicPortMirroringNetwork.isSetId()) {
                        if (!Objects.equals(profileNetwork.getId(), nicPortMirroringNetwork.getId())) {
                            return false;
                        }
                    }
                    if (nicPortMirroringNetwork.isSetName()) {
                        if (!Objects.equals(profileNetwork.getName(), nicPortMirroringNetwork.getName())) {
                            return false;
                        }
                    }
                }
            }
        }

        // All checks passed, so the profile is compatible:
        return true;
    }
}
